# Digital Rust UI Specification

Part of Digital Rust Specification v1.2.0

This document is the canonical specification for Digital Rust as a **user interface
theme**: surfaces, elevation, interaction physics, and component definitions. It is
a sibling to the *Digital Rust Syntax Highlighting Specification*, which owns the
colour palette, token classification, editor chrome, and terminal configuration.
The palette itself is defined there and is not re-specified here; this document
references colours by name only.

A conforming UI implementation uses the named colours from the syntax
specification, and follows the component, elevation, and interaction rules below.

## Contents

- [Design Principles](#design-principles)
- [Surfaces & Texture](#surfaces--texture)
- [Elevation](#elevation)
- [Interaction Physics](#interaction-physics)
- [Typography in UI](#typography-in-ui)
- [Components](#components)
  - [Buttons](#buttons)
  - [Inputs & Text Areas](#inputs--text-areas)
  - [Checkboxes](#checkboxes)
  - [Toggles](#toggles)
  - [Sliders](#sliders)
  - [Tabs](#tabs)
  - [Menus & List Boxes](#menus--list-boxes)
  - [Modals & Dialogs](#modals--dialogs)
  - [Toasts & Notices](#toasts--notices)
  - [Tooltips & Popovers](#tooltips--popovers)
  - [Badges & Tags](#badges--tags)
  - [Callouts & Panels](#callouts--panels)
  - [Scrollbars](#scrollbars)
  - [Links](#links)
  - [Status Bars](#status-bars)
  - [Tables](#tables)
- [States](#states)
- [Accessibility](#accessibility)
- [Do's and Don'ts](#dos-and-donts)

## Design Principles

Digital Rust interfaces are **tactile panels in a failing server rack**. Every
interactive element should feel like a physical piece of chunky old hardware:
a switch you can press, a bezel that catches light, a surface that has weight.

Five rules carry the whole system:

1. **Everything physical has an edge.** Interactive elements carry 2px Dark
   Packet borders. Borders are walls, not decoration.
2. **Everything raised casts a hard shadow.** Offset solid shadows, no blur,
   drawn in Bad Sector. Light in this system is directional and warm.
3. **Everything raised catches light.** Elevated and interactive elements carry
   the inner highlight, `inset 0 1px 0 rgba(255, 255, 255, 0.1)`, a faint
   light-line along the top edge. This is the bevel of the panel.
4. **Everything pressable presses.** Rest has offset; hover grows the offset and
   lifts the element; active collapses the offset and drives the element into
   the rack. No interactive element is allowed a static hover.
5. **Nothing is flat.** Surfaces carry structural noise and scanlines. The only
   exemptions are interactive text surfaces (inputs, text areas, editable
   regions), which stay clean for legibility, and `prefers-reduced-motion`.

The system rejects: blurred drop shadows, pure black (`#000`) anywhere,
border-radius above 6px, hairline decorative borders, and any cold blue-purple
dark-theme material. These are defined in the syntax specification's named
rules and apply equally to UI.

## Surfaces & Texture

The rust ramp is the only neutral scale. Semantic surface tokens map onto it:

| Token | Colour | Role |
| --- | --- | --- |
| `--dr-surface-base` | Event Horizon `#110402` | Page background; input wells; code blocks |
| `--dr-surface-secondary` | Null Pointer `#230905` | Elevated chrome: tab bars, gutters, sidebars |
| `--dr-surface-elevated` | Core Meltdown `#340d07` | Cards, popovers, menus, modals, filled hovers |
| `--dr-surface-highlight` | Bad Sector `#46120a` | Hover fills, active list items, shadow colour |
| `--dr-surface-active` | `#5a1810` | Scrollbar hover, pressed wells |

**The Recessed Well Rule.** Inputs and editable regions sit *below* the panel,
not above it. A well uses the darkest surface (Event Horizon), a 2px Bad Sector
border, and no offset shadow. Wells never rise on hover; they respond through
border colour, not elevation. (See [Inputs](#inputs--text-areas).)

**Texture exemption.** The Flat Surface Failure Rule (syntax specification)
applies to all *ambient* surfaces: page backgrounds, cards, panels, modals.
Interactive text surfaces — inputs, text areas, code editors — are exempt.
Texture inside a text field fights the text; legibility outranks atmosphere.

## Elevation

All structural shadows are offset solids in Bad Sector (`--dr-surface-highlight`)
with zero blur. Light is always top-left; shadows fall down-right.

| Token | Value | Use |
| --- | --- | --- |
| `--dr-shadow-offset-sm` | `2px 2px 0 0` | Resting interactive elements: buttons, checkboxes, toggles |
| `--dr-shadow-offset-md` | `3px 3px 0 0` | Raised containers: menus, toasts, callouts, terminals |
| `--dr-shadow-offset-lg` | `4px 4px 0 0` | Highest layer: modals, lightboxes, drag ghosts |
| `--dr-shadow-hover-md` | `4px 4px 0 0` | Hover state for elements resting at `offset-sm` |
| `--dr-shadow-active` | `0 0 0 0` | Pressed state; the element sits flush |
| `--dr-shadow-glow-sm` | `0 0 10px rgba(175, 46, 26, 0.4)` | Radiated heat on primary hover / focused accents |
| `--dr-shadow-glow-md` | `0 0 20px rgba(175, 46, 26, 0.4)` | Hero-level emphasis only |
| `--dr-shadow-inner-highlight` | `inset 0 1px 0 rgba(255, 255, 255, 0.1)` | Top-edge bevel on raised/interactive elements |
| `--dr-shadow-inner-lowlight` | `inset 0 -1px 0 rgba(17, 4, 2, 0.6)` | Bottom-edge shade for wells and thumbs |
| `--dr-shadow-accent` | `5px 5px 0 0` Digital Rust | Brand-level emphasis (rare) |

**The No Blur Rule.** Blur radius is zero everywhere except the glow shadows,
which simulate radiated heat, not material depth.

**The Bevel Rule.** The inner highlight accompanies every offset shadow on an
interactive or elevated element. An element with `offset-sm/md/lg` and no inner
highlight is unfinished. The two are stacked:

```css
box-shadow:
	var(--dr-shadow-offset-sm),
	var(--dr-shadow-inner-highlight);
```

## Interaction Physics

The press cycle is the system's signature motion. It applies to buttons,
checkboxes, chips, tabs, and any element that behaves like a switch.

| State | Transform | Shadow |
| --- | --- | --- |
| Rest | `translate(0, 0)` | resting offset (`offset-sm` or `offset-md`) + inner highlight |
| Hover | `translate(-2px, -2px)` | offset grows 2px per axis (`hover-md`) + inner highlight |
| Active | `translate(2px, 2px)` | `0 0 0 0`; element drives into the rack |

- Transitions use `--dr-duration-fast` (150ms) with
  `--dr-ease-default` (`cubic-bezier(0.4, 0, 0.2, 1)`).
- The element translates *with* the shadow change: hover lifts toward the light
  (up-left), active presses away from it (down-right). The shadow offset and the
  translation are always the same magnitude and direction.
- **Focus visible**: 2px Stack Overglow outline, 2px offset. Focus never removes
  the resting shadow. Keyboard focus and mouse hover are independent states.
- **Disabled**: no transform, no shadow, muted colours, `cursor: not-allowed`.
  A dead switch does not click.
- All of the above is suppressed under `prefers-reduced-motion: reduce`: the
  full state *colours* still apply, but transform and transition are removed.

```css
.dr-button {
	transition:
		transform var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast),
		background-color var(--dr-transition-fast),
		border-color var(--dr-transition-fast),
		color var(--dr-transition-fast);
}
.dr-button:hover  { transform: var(--dr-press-hover); }
.dr-button:active { transform: var(--dr-press-active); }
```

## Typography in UI

UI chrome speaks in Rajdhani, all-caps, tracked; content speaks in Zilla
Slab; values and readouts speak in IBM Plex Mono. These roles come from the
design system and are restated here only as they apply to components:

- **Button and tab labels**: Rajdhani 600–700, uppercase, 0.05em tracking,
  verb + object ("Save changes", not "OK").
- **Badges and tags**: Rajdhani 700, uppercase, 0.08em tracking, 0.75rem.
- **Panel and card titles**: Rajdhani 700, uppercase, 0.05em tracking,
  Fatal Error colour (the One Heading Colour Rule).
- **Inputs and text areas**: body font (Zilla Slab), 1rem, no uppercase.
  Placeholder text is muted and italic.
- **Readouts, status bars, hex values, version stamps**: IBM Plex Mono
  0.875rem or smaller, tabular numerals.

## Components

### Buttons

The signature component. A hardware panel switch: thick edge, hard shadow,
light-catching bevel, physical travel.

**Base (all buttons)**

| Property | Value |
| --- | --- |
| Padding | `0.5rem 1.5rem` (`--dr-space-sm --dr-space-lg`) |
| Border | `2px solid` (colour per variant) |
| Radius | `4px` (`--dr-radius`) |
| Font | Rajdhani 600, uppercase, `0.05em` tracking, `--dr-text-sm` |
| Shadow | resting offset + inner highlight |
| Cursor | `pointer` |
| Interaction | full press cycle at 150ms |

**Variants**

| Variant | Rest | Hover | Active |
| --- | --- | --- | --- |
| **Default / Ghost** | Transparent bg, Phosphor Smoke text, Dark Packet border, `offset-sm` | Core Meltdown fill, Ghost Pixel text, Digital Rust border, `hover-md`, lifts | `offset` collapses, presses in |
| **Primary** | Digital Rust bg, Ghost Pixel text, Digital Rust border, `offset-md` | Amber Alert bg and border, `hover-md` + `glow-sm`, lifts | `offset` collapses, glow removed, presses in |
| **Success** | Memory Leek border and text | Memory Leek bg, Event Horizon text | presses in |
| **Warning** | Stack Overglow border and text | Stack Overglow bg, Event Horizon text | presses in |
| **Error** | Amber Alert border, Amber Alert Text (`#cb6353`) text | Amber Alert bg, Event Horizon text | presses in |
| **Disabled** | Bad Sector bg, muted text, no border colour shift | no response | no response; `cursor: not-allowed` |

Reference CSS:

```css
.dr-btn {
	padding: var(--dr-space-sm) var(--dr-space-lg);
	border: 2px solid var(--dark-packet);
	border-radius: var(--dr-radius);
	background: transparent;
	color: var(--dr-text-secondary);
	font-family: var(--dr-font-heading);
	font-weight: 600;
	font-size: var(--dr-text-sm);
	text-transform: uppercase;
	letter-spacing: 0.05em;
	box-shadow: var(--dr-shadow-offset-sm), var(--dr-shadow-inner-highlight);
	cursor: pointer;
	transition:
		transform var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast),
		background-color var(--dr-transition-fast),
		border-color var(--dr-transition-fast),
		color var(--dr-transition-fast);
}
.dr-btn:hover {
	background: var(--dr-surface-elevated);
	border-color: var(--digital-rust);
	color: var(--dr-text-primary);
	transform: var(--dr-press-hover);
	box-shadow: var(--dr-shadow-hover-md), var(--dr-shadow-inner-highlight);
}
.dr-btn:active {
	transform: var(--dr-press-active);
	box-shadow: var(--dr-shadow-active);
}
.dr-btn:focus-visible {
	outline: var(--dr-focus-ring);
	outline-offset: var(--dr-focus-ring-offset);
}

.dr-btn-primary {
	background: var(--digital-rust);
	border-color: var(--digital-rust);
	color: var(--dr-text-on-accent);
	box-shadow: var(--dr-shadow-offset-md), var(--dr-shadow-inner-highlight);
}
.dr-btn-primary:hover {
	background: var(--amber-alert);
	border-color: var(--amber-alert);
	transform: var(--dr-press-hover);
	box-shadow:
		var(--dr-shadow-hover-md),
		var(--dr-shadow-glow-sm),
		var(--dr-shadow-inner-highlight);
}
```

**Rules**

- One primary button per view. Primary carries the brand; two primaries on one
  screen is a visual shouting match.
- Labels are short verbs or verb + object, never "OK" / "Submit" / "Click here".
- Icon + text buttons keep the icon at text size, `0.25rem` gap.
- Full-pill radius is forbidden on buttons; `4px` only.

### Inputs & Text Areas

Recessed wells. The darkest surface on the page; the user types into the machine.

| Property | Value |
| --- | --- |
| Background | Event Horizon (`--dr-surface-base`) |
| Border | `2px solid` Bad Sector (`--dr-surface-highlight`) |
| Radius | `4px` |
| Padding | `0.5rem 0.75rem` |
| Font | body font, `--dr-text-base`, Ghost Pixel text |
| Shadow | none at rest (wells do not rise) |
| Placeholder | muted text (`rgba(239, 213, 209, 0.5)`), italic |
| Hover (unfocused) | border shifts to Dark Packet |
| Focus | border shifts to Digital Rust; 2px Stack Overglow outline at 2px offset |
| Disabled | transparent border, muted text, no shadow |
| Invalid | Amber Alert border; error message in Amber Alert Text |

```css
.dr-input {
	background: var(--dr-surface-base);
	border: 2px solid var(--dr-surface-highlight);
	border-radius: var(--dr-radius);
	padding: var(--dr-space-sm) 0.75rem;
	color: var(--dr-text-primary);
	font-family: var(--dr-font-body);
	font-size: var(--dr-text-base);
	transition:
		border-color var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast);
}
.dr-input:hover:not(:focus) { border-color: var(--dark-packet); }
.dr-input:focus {
	outline: var(--dr-focus-ring);
	outline-offset: var(--dr-focus-ring-offset);
	border-color: var(--digital-rust);
}
.dr-input::placeholder {
	color: var(--dr-text-muted, rgba(239, 213, 209, 0.5));
	font-style: italic;
}
```

**Rules**

- Wells are exempt from noise/scanline texture (see The Recessed Well Rule).
- No offset shadows on wells. Depth on inputs is communicated by darkness,
  not elevation.
- Labels sit above the input in Rajdhani uppercase at `--dr-text-xs`,
  Phosphor Smoke.

### Checkboxes

A small square switch with the full press cycle. 16px box, 2px radius.

| State | Style |
| --- | --- |
| Rest | Event Horizon bg, 2px Dark Packet border, `offset-sm` shadow |
| Hover | Digital Rust border, lifts (`-2px, -2px`), `hover-md` |
| Active | presses (`2px, 2px`), shadow collapses |
| Checked | Digital Rust bg and border; Ghost Pixel ✓ mark, 700 weight |
| Focus visible | 2px Stack Overglow outline, 2px offset |
| Disabled | no shadow, muted border, `not-allowed` |

The ✓ glyph is drawn centred, 12px at 16px box size. Indeterminate state uses a
Ghost Pixel em-dash block in place of the check.

### Toggles

A sliding rack switch. Track: Bad Sector bg at rest, Digital Rust when enabled,
always with a 2px Dark Packet border and `offset-sm` shadow. Thumb: Ghost Pixel,
2px radius, its own `1px 1px 0 0` offset in Bad Sector plus inner lowlight.
The thumb slides at 150ms; the track does not translate.

### Sliders

Track: 4px-high Event Horizon well with 1px Bad Sector border. Fill (the
"used" portion): Digital Rust. Thumb: 16px square, 2px radius, Ghost Pixel with
2px Dark Packet border and `offset-sm` shadow; lifts on hover-drag, presses on
active-drag. Thumb never exceeds 2px radius — sliders are machined parts,
not pills.

### Tabs

Tab bar: Core Meltdown bg with a 2px Dark Packet bottom border. Tabs: Phosphor
Smoke text, 2px radius top corners only. Hover: Ghost Pixel text, Bad Sector bg.
Active tab: Null Pointer bg (surface-secondary), Ghost Pixel text, and a 2px
Digital Rust bottom border that overlaps the bar's border — the active tab is
the one still receiving power. Modified/unsaved indicator: Stack Overglow dot
or `● MOD` label.

### Menus & List Boxes

Container: Core Meltdown bg, 2px Dark Packet border, 4px radius,
`offset-md` + inner highlight, `0.25rem` padding. Items: Phosphor Smoke, 2px
radius, `0.25rem 0.5rem` padding. Hover/selected item: Bad Sector bg, Ghost
Pixel text. Separators: 1px Bad Sector line, full-bleed. Menus never blur the
content behind them; they are solid panels bolted over it.

### Modals & Dialogs

The highest layer. Core Meltdown bg, 2px Dark Packet border, 4px radius,
`offset-lg` + inner highlight. Title in Fatal Error, Rajdhani 700 uppercase.
Backdrop: warm-tinted scrim, `rgba(17, 4, 2, 0.7)` — never pure black, never
blurred. The footer action row carries at most one primary button. Modals do not
animate in with scale/bounce; a 150ms fade is the only permitted entry motion.

### Toasts & Notices

Bricked Board bg (a hotter panel than modals — notices radiate residual heat),
Ghost Pixel text, 2px Dark Packet border, 4px radius, `offset-md` + inner
highlight. Optional 3px left accent border in the semantic colour (Memory Leek
success, Stack Overglow warning, Amber Alert error, Dead Thread info). Auto-
dismiss toasts show a thin Digital Rust progress line along the bottom edge.

### Tooltips & Popovers

Core Meltdown bg, 2px Dark Packet border, 4px radius, `offset-md` + inner
highlight. Text: Phosphor Smoke at `--dr-text-sm`. No arrow stems; the offset
shadow alone establishes which surface the tooltip floats above. Hover-preview
popovers (rich content) use `offset-lg` and may contain panels; plain text
tooltips use `offset-md`.

### Badges & Tags

Semantic status chips: small, dense, tracked. 4px radius,
`0.2rem 0.6rem` padding, Rajdhani 700 uppercase, 0.08em tracking, 0.75rem.

| Kind | Text | Background | Border |
| --- | --- | --- | --- |
| Add / Success | Memory Leek | 15% Memory Leek | 1px Memory Leek 30% |
| Modified / Info | Dead Thread | 15% Dead Thread | 1px Dead Thread 30% |
| Delete / Error | Amber Alert Text | 10% Amber Alert | 1px Amber Alert 25% |
| Warning | Stack Overglow | 15% Stack Overglow | 1px Stack Overglow 30% |

Badges are static indicators: no press cycle, no shadow. Full-pill radius is
permitted on badges only (they are rivets, not panels).

### Callouts & Panels

Core Meltdown bg, 2px Dark Packet border, 4px radius, `offset-md` + inner
highlight, `1.5rem` internal padding. Semantic variants tint the title and add a
3px left border in Dead Thread (note/info), Stack Overglow (warning), or Amber
Alert (error/danger). The 3px left accent on callouts is a deliberate exception
to the no-accent-stripe rule; it exists so semantic blocks are identifiable at
scroll speed. Panels that respond to hover (cards, palette chips) use the press
cycle with `offset-sm` rest.

### Scrollbars

Thin (8-10px). Track: transparent. Thumb: Dark Packet at rest, Daemon Blood on
hover, surface-active (`#5a1810`) on drag. 2px radius. Scrollbars are machine
rails: they never glow, never widen past 12px, and never disappear on idle
(visibility of scroll position is a readability concern).

### Links

Default: Phantom Current, 1px underline, 3px offset. Hover: Fatal Error, 2px
underline. Visited: Corrupted Cache. Active: Digital Rust. Focus: 2px Stack
Overglow outline, 2px offset. Underline style carries semantics: solid for
links, wavy for diagnostics. Never style a diagnostic squiggle as a link or
vice versa.

### Status Bars

Bricked Board bg, Ghost Pixel text, 2px Dark Packet top border, IBM Plex Mono at
`--dr-text-xs`, `0.25rem 0.5rem` item padding. Items respond to hover with
Phantom Current text only — status bar items do not lift. Semantic items:
Memory Leek (success/OK), Stack Overglow (warning), Amber Alert Text (error).

### Tables

Header row: Rajdhani 700 uppercase `--dr-text-xs`, Fatal Error text, 2px Dark
Packet bottom border. Body: Zilla Slab, Ghost Pixel. Row separators: 1px Bad
Sector. Row hover: Bad Sector bg. Zebra striping is forbidden (the scanline
overlay already bands the page; double banding reads as interference).

## States

| State | Universal rule |
| --- | --- |
| Hover | Lift toward the light (`-2px, -2px`) + offset grows. Non-pressable surfaces (rows, list items) respond with Bad Sector fill instead of lift. |
| Active (press) | Drive into the rack (`2px, 2px`) + offset collapses to zero. |
| Focus visible | 2px Stack Overglow outline, 2px offset, *in addition to* resting shadow. Never `outline: none` without replacement. |
| Selected | Bad Sector bg + Ghost Pixel text (lists, menus, tabs use tab rules). |
| Disabled | Bad Sector or transparent bg, muted text, no shadow, no transform, `cursor: not-allowed`. |
| Loading / Processing | Stack Overglow with motion (spinner, pulse); under reduced motion, static Stack Overglow text label. |
| Invalid / Error | Amber Alert border on the control + Amber Alert Text message. Errors never rely on colour alone: icon or text accompanies. |

## Accessibility

- WCAG 2.1 AA contrast is required on all text, as defined in the syntax
  specification (Ghost Pixel on Event Horizon, 17.17:1, is the baseline).
- Amber Alert Text (`#cb6353`) is the AA-compliant text variant of Amber Alert;
  use it for error text on base/elevated surfaces, never the raw `#bf5747`.
- Every interactive element is reachable and operable by keyboard, with a
  visible focus indicator (2px Stack Overglow, 2px offset).
- State is never communicated by colour alone: errors carry icons or text,
  diffs carry +/− glyphs, focus carries an outline.
- `prefers-reduced-motion: reduce` suppresses all transforms, transitions, the
  cursor blink, and the scanline/noise overlays. All state *styling* remains.
- Touch targets are at least 24×24px; the press cycle's 2px travel does not
  count toward target size.

## Do's and Don'ts

### Do

- **Do** stack the inner highlight under every offset shadow on interactive and
  elevated elements. The bevel is half the material.
- **Do** run the full press cycle on every pressable element: rest offset,
  hover lift, active collapse.
- **Do** keep wells (inputs) dark and recessed; depth through darkness, not
  elevation.
- **Do** give every modal exactly one primary action.
- **Do** use Bricked Board for toasts and status bars; it reads as a hotter,
  more urgent panel than Core Meltdown.
- **Do** keep every border in Dark Packet at 2px unless the component spec says
  otherwise.

### Don't

- **Don't** blur any shadow. Glows simulate heat; nothing else blurs.
- **Don't** use pure black (`#000`) anywhere, including scrims and backdrops.
  Warm near-blacks only.
- **Don't** give inputs offset shadows or lift them on hover. Wells stay in
  the rack.
- **Don't** use radius above 6px on any component; 4px is the default, 2px for
  small machined parts (checkboxes, toggle thumbs, slider thumbs, menu items).
- **Don't** animate modals or toasts with scale, spring, or bounce. A 150ms
  fade is the maximum drama permitted.
- **Don't** remove focus outlines. Stack Overglow focus rings are the system's
  one non-negotiable courtesy.
- **Don't** place two primary buttons in one view.
- **Don't** apply the press cycle to passive content (headings, body text,
  badges). Static things stay bolted down.
